/*
AcessOS 0.01
Keyboard Driver

Handles Keyboard Interrupts and sets global
variable `kb_lastChar` for tty driver.
*/
#include <acess.h>
#include <fs_devfs2.h>
#include <keysym.h>
#include <tpl_drv_keyboard.h>
#include <drivers/kbdus.h>
#include <drivers/kbdus2.h>

#define	KB_BUFFER_SIZE	1024

// === PROTOTYPES ===
void Keyboard_Handler(struct regs *r);
void Keyboard_AddBuffer(char ch);
 int Keyboard_ReadBuffer(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Dest);
void Keyboard_UpdateLEDs();
 int Keyboard_IOCtl(vfs_node *Node, int Id, void *Data);

// === GLOBALS ===
Uint8	**gpKeyMap = gpKBDUS;
//Uint32	gbaKeystates_[8];	//0-256
Uint8	gbaKeystates[256];
 int	gbKbShiftState = 0;
 int	gbKbCapsState = 0;
 int	kb_lastChar = -1;
 int	gbKeyUp = 0;
 int	giKeyLayer = 0;
Uint8	gaKB_Buffer[KB_BUFFER_SIZE];	//!< Keyboard Ring Buffer
volatile int	giKB_InsertPoint = 0;	//!< Writing location marker
volatile int	giKB_ReadPoint = 0;	//!< Reading location marker
volatile int	giKB_InUse = 0;		//!< Lock marker
devfs_driver	gKB_Device = {
	{
		.name = "keyboard", .nameLength = 8,
		.flags = VFS_FFLAG_READONLY,
		.mode = 0666,
		.length = -1,
		.read = Keyboard_ReadBuffer
	}, Keyboard_IOCtl
};

// === CODE ==

/**
 \fn void keyboard_install()
 \brief Installs Keyboard driver
*/
void Keyboard_Install()
{
    irq_install_handler(1, Keyboard_Handler);
	dev_addDevice( &gKB_Device );
}

/**
 * \fn void Keyboard_Handler(struct regs *r)
 */
void Keyboard_Handler(struct regs *r)
{
	Uint8	scancode;
	Uint8	ch;

	//if( inportb(0x64) & 0x20 )	return;
	
	scancode = inportb(0x60); // Read from the keyboard's data buffer

	if(scancode == 0xFA)
	{
		kb_lastChar = KB_ACK;
		return;
	}
	
	if(scancode == 0xE0)
	{
		giKeyLayer = 1;
		return;
	}
	if(scancode == 0xE1)
	{
		giKeyLayer = 2;
		return;
	}
	
	#if KB_ALT_SCANCODES
	if(scancode == 0xF0)
	{
		gbKeyUp = 1;
		return;
	}
	#else
	if(scancode & 0x80)
	{
		scancode &= 0x7F;
		gbKeyUp = 1;
	}
	#endif
	
	// Translate 
	ch = gpKeyMap[giKeyLayer][scancode];
	// Check for unknown key
	if(!ch && !gbKeyUp)	LogF("UNK %i %x\n", giKeyLayer, scancode);
	giKeyLayer = 0;
	
	if (gbKeyUp)
	{
		gbKeyUp = 0;
		//gbaKeystates[ ch>>5 ] &= !(1 << (ch&0x1F));	// Unset the bit relating to the key
		gbaKeystates[ ch ] = 0;	// Unset key state flag
		
		//if( !(gbaKeystates[KEY_LSHIFT>>5] & (1 << (KEY_LSHIFT&0x1F)))
		// && !(gbaKeystates[KEY_RSHIFT>>5] & (1 << (KEY_RSHIFT&0x1F))) )
		if( !gbaKeystates[KEY_LSHIFT] && !gbaKeystates[KEY_RSHIFT] )
			gbKbShiftState = 0;
		
		Keyboard_AddBuffer(KB_KEYUP);
		Keyboard_AddBuffer(ch);
		
		return;
	}

	// Set the bit relating to the key
	//gbaKeystates[ch>>5] |= 1 << (ch&0x1F);
	gbaKeystates[ch] = 1;
	if(ch == KEY_LSHIFT || ch == KEY_RSHIFT)
		gbKbShiftState = 1;
		
	if(ch == KEY_CAPSLOCK) {
		gbKbCapsState = !gbKbCapsState;
		Keyboard_UpdateLEDs();
	}

	// Ignore Non-Printable Characters
	if(ch == 0 || ch & 0x80)		return;
		
	// Is shift pressed
	if(gbKbShiftState ^ gbKbCapsState)
	{
		switch(ch)
		{
		case '`':	ch = '~';	break;
		case '1':	ch = '!';	break;
		case '2':	ch = '@';	break;
		case '3':	ch = '#';	break;
		case '4':	ch = '$';	break;
		case '5':	ch = '%';	break;
		case '6':	ch = '^';	break;
		case '7':	ch = '&';	break;
		case '8':	ch = '*';	break;
		case '9':	ch = '(';	break;
		case '0':	ch = ')';	break;
		case '-':	ch = '_';	break;
		case '=':	ch = '+';	break;
		case '[':	ch = '{';	break;
		case ']':	ch = '}';	break;
		case '\\':	ch = '|';	break;
		case ';':	ch = ':';	break;
		case '\'':	ch = '"';	break;
		case ',':	ch = '<';	break;
		case '.':	ch = '>';	break;
		case '/':	ch = '?';	break;
		default:
			if('a' <= ch && ch <= 'z')
				ch -= 0x20;
			break;
		}
	}
	
	Kernel_KeyEvent(gbaKeystates, ch);
	
	kb_lastChar = ch;
	
	//LogF("Keyboard_Handler: ch='%c'(0x%x)\n", ch, ch);
}

/**
 * \fn void Keyboard_AddBuffer(char ch)
 * \brief Add to the keyboard ring buffer
 */
void Keyboard_AddBuffer(char ch)
{
	// Add to buffer
	gaKB_Buffer[ giKB_InsertPoint++ ] = ch;
	// - Wrap
	if( giKB_InsertPoint == KB_BUFFER_SIZE )	giKB_InsertPoint = 0;
	// - Force increment read pointer
	if( giKB_InsertPoint == giKB_ReadPoint )	giKB_ReadPoint ++;
}

/**
 * \fn int Keyboard_ReadBuffer(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Dest)
 * \brief Read from the ring buffer
 * \param Node	Unused
 * \param Offset	Unused (Character Device)
 * \param Length	Number of bytes to read
 * \param Dest	Destination
 */
int Keyboard_ReadBuffer(vfs_node *Node, Uint64 Offset, Uint64 Length, void *Dest)
{
	 int	pos = 0;
	char	*dstbuf = Dest;
	
	if(giKB_InUse)	return -1;
	giKB_InUse = 1;
	
	while(giKB_ReadPoint != giKB_InsertPoint && pos < Length)
	{
		dstbuf[pos++] = gaKB_Buffer[ giKB_ReadPoint++ ];
		if( giKB_ReadPoint == KB_BUFFER_SIZE )	giKB_InsertPoint = 0;
	}
	
	giKB_InUse = 0;
	
	return Length;
}

/**
 * \fn int Keyboard_IOCtl(vfs_node *Node, int Id, void *Data)
 */
int Keyboard_IOCtl(vfs_node *Node, int Id, void *Data)
{
	switch(Id)
	{
	case DRV_IOCTL_TYPE:	return DRV_TYPE_KEYBOARD;
	case DRV_IOCTL_IDENT:	memcpy(Data, "KB\0\0", 4);	return 1;
	case DRV_IOCTL_VERSION:	return 0x100;
	case DRV_IOCTL_LOOKUP:	return GetIOCtlId(DRV_TYPE_KEYBOARD, Data);
	
	default:	return 0;
	}
}

/**
 * \fn void Keyboard_UpdateLEDs()
 * \brief Updates the status of the keyboard LEDs
 */
void Keyboard_UpdateLEDs()
{
	Uint8	leds;
	
	leds = (gbKbCapsState ? 4 : 0);
	
	while( inportb(0x64) & 2 );	// Wait for bit 2 to unset
	outportb(0x60, 0xED);	// Send update command
	
	while( inportb(0x64) & 2 );	// Wait for bit 2 to unset
	outportb(0x60, leds);
}

